package com.huahai.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.huahai.constant.RedisConstant;
import com.huahai.exception.ErrorCode;
import com.huahai.pojo.entity.Liked;
import com.huahai.pojo.enums.LikedTargetEnums;
import com.huahai.service.CommentService;
import com.huahai.service.LikedService;
import com.huahai.service.PictureService;
import com.huahai.util.ThrowUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import static com.huahai.constant.RedisConstant.PROJECT_NAME;

/**
 * 定时任务处理类
 *
 * @author huahai
 */
@Slf4j
@Component
public class ScheduleHandle {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private PictureService pictureService;

    @Resource
    private LikedService likedService;

    @Resource
    private CommentService commentService;

    // -------------------------- 定义图片/评论的Redis Key前缀 --------------------------
    // 图片点赞：用户列表Key前缀
    private final String PICTURE_LIKED_USER_PREFIX = RedisConstant.PROJECT_NAME + RedisConstant.LIKED_USER_LIST;
    // 图片点赞：数量Key前缀
    private final String PICTURE_LIKE_COUNT_PREFIX = RedisConstant.PROJECT_NAME + RedisConstant.LIKE_COUNT;
    // 评论点赞：用户列表Key前缀
    private final String COMMENT_LIKED_USER_PREFIX = RedisConstant.PROJECT_NAME + RedisConstant.COMMENT_LIKED_USER_LIST;
    // 评论点赞：数量Key前缀
    private final String COMMENT_LIKE_COUNT_PREFIX = RedisConstant.PROJECT_NAME + RedisConstant.COMMENT_LIKE_COUNT;


    /**
     * 处理图片浏览量
     */
    @Scheduled(cron = "0 0 22 * * *")  // 每日 22 时 0 分
    public void handlePictureViewCount() {
        log.info("开始处理图片浏览量");
        // 获取图片浏览量的 key
        String viewKey = PROJECT_NAME + RedisConstant.VIEW_COUNT;
        // 从 redis 里获取图片浏览量的集合
        Set<String> pictureViewCountSet = stringRedisTemplate.keys(viewKey + "*");
        Map<Long, Long> pictureViewCountMap = new HashMap<>();
        if (CollUtil.isNotEmpty(pictureViewCountSet)) {
            // 获取全部图片的浏览量进行映射
            for (String key : pictureViewCountSet) {
                Long pictureId = Long.valueOf(key.substring(viewKey.length()));
                String viewCountStr = stringRedisTemplate.opsForValue().get(viewKey + pictureId);
                if (StrUtil.isBlank(viewCountStr)) {
                    continue;
                }
                Long viewCount = Long.valueOf(viewCountStr);
                pictureViewCountMap.put(pictureId, viewCount);
            }
            // 批量更新数据库图片浏览量信息
            pictureViewCountMap.forEach((pictureId, viewCount) -> {
                pictureService.update()
                        .eq("id", pictureId)
                        .set("viewCount", viewCount)
                        .update();
            });
            // 删除缓存的浏览次数信息
            stringRedisTemplate.delete(pictureViewCountSet);
        }

    }

    /**
     * 统一处理图片、评论的点赞量和点赞列表同步
     * 每隔三分钟执行一次（保持原有定时策略）
     */
    @Scheduled(cron = "0 0/3 * * * ?")
    @Transactional(rollbackFor = Exception.class)
    public void handleLikeCountAndLikedList() {
        log.info("开始同步点赞数据（图片+评论）...");
        long startTime = System.currentTimeMillis();

        try {
            // 1. 处理图片点赞同步
            // 1.1 从Redis获取所有图片点赞用户列表Key，提取图片ID
            Set<String> pictureLikedKeys = stringRedisTemplate.keys(PICTURE_LIKED_USER_PREFIX + "*");
            Set<Long> redisPictureIds = extractTargetIdsFromRedisKeys(pictureLikedKeys, PICTURE_LIKED_USER_PREFIX);
            // 1.2 从数据库获取所有有图片点赞记录的图片ID（target=0）
            List<Long> dbPictureIds = getDbTargetIds(LikedTargetEnums.PICTURE.getValue(), "pictureId");
            // 1.3 合并需处理的图片ID（Redis存在 + 数据库存在）
            Set<Long> allPictureIds = mergeTargetIds(redisPictureIds, dbPictureIds);
            // 1.4 同步图片点赞数据（调用通用方法）
            if (!allPictureIds.isEmpty()) {
                log.info("待同步图片点赞数据：{}个图片", allPictureIds.size());
                // 图片点赞数更新逻辑（BiFunction：参数 1 = 图片 ID，参数 2 = 点赞数，返回是否成功）
                BiFunction<Long, Long, Boolean> pictureCountUpdateFunc = (pictureId, likeCount) ->
                        pictureService.update().eq("id", pictureId).set("likeCount", likeCount).update();
                handleTargetLikeSync(
                        // 点赞类型：图片
                        LikedTargetEnums.PICTURE.getValue(),
                        PICTURE_LIKED_USER_PREFIX,
                        PICTURE_LIKE_COUNT_PREFIX,
                        pictureCountUpdateFunc,
                        // 待处理图片ID集合
                        allPictureIds
                );
            } else {
                log.info("无待同步的图片点赞数据");
            }

            // 2. 处理评论点赞同步
            // 2.1 从Redis获取所有评论点赞用户列表Key，提取评论ID
            Set<String> commentLikedKeys = stringRedisTemplate.keys(COMMENT_LIKED_USER_PREFIX + "*");
            Set<Long> redisCommentIds = extractTargetIdsFromRedisKeys(commentLikedKeys, COMMENT_LIKED_USER_PREFIX);
            // 2.2 从数据库获取所有有评论点赞记录的评论ID（target=1）
            List<Long> dbCommentIds = getDbTargetIds(LikedTargetEnums.COMMENT.getValue(), "commentId");
            // 2.3 合并需处理的评论ID（Redis存在 + 数据库存在）
            Set<Long> allCommentIds = mergeTargetIds(redisCommentIds, dbCommentIds);
            // 2.4 同步评论点赞数据（调用通用方法）
            if (!allCommentIds.isEmpty()) {
                log.info("待同步评论点赞数据：{}个评论", allCommentIds.size());
                // 评论点赞数更新逻辑（BiFunction：参数1=评论ID，参数2=点赞数，返回是否成功）
                BiFunction<Long, Long, Boolean> commentCountUpdateFunc = (commentId, likeCount) ->
                        commentService.update().eq("id", commentId).set("likeCount", likeCount).update();

                handleTargetLikeSync(
                        // 点赞类型：评论
                        LikedTargetEnums.COMMENT.getValue(),
                        COMMENT_LIKED_USER_PREFIX,
                        COMMENT_LIKE_COUNT_PREFIX,
                        commentCountUpdateFunc,
                        // 待处理评论ID集合
                        allCommentIds
                );
            } else {
                log.info("无待同步的评论点赞数据");
            }
            log.info("点赞数据同步完成（图片+评论），总耗时{}ms", System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("点赞数据同步失败（图片+评论）", e);
            // 抛出异常触发事务回滚
            throw e;
        }
    }

    /**
     * 从 Redis 的 Key 中提取目标 ID（如：Key=prefix123 → ID=123）
     *
     * @param redisKeys Redis中的Key集合
     * @param keyPrefix Key前缀（如PICTURE_LIKED_USER_PREFIX）
     * @return 目标ID集合（Long类型）
     */
    private Set<Long> extractTargetIdsFromRedisKeys(Set<String> redisKeys, String keyPrefix) {
        if (Objects.isNull(redisKeys) || redisKeys.isEmpty()) {
            return new HashSet<>();
        }
        return redisKeys.stream()
                // 截取前缀后的ID
                .map(key -> Long.parseLong(key.substring(keyPrefix.length())))
                .collect(Collectors.toSet());
    }

    /**
     * 从 liked 表查询指定类型（图片/评论）的目标ID集合
     *
     * @param target         点赞类型（0=图片，1=评论）
     * @param targetIdColumn 目标ID对应的数据库字段（如"pictureId"、"commentId"）
     * @return 目标ID列表（Long类型）
     */
    private List<Long> getDbTargetIds(Integer target, String targetIdColumn) {
        List<Object> dbObjList = likedService.listObjs(
                new QueryWrapper<Liked>()
                        .select("distinct " + targetIdColumn) // 去重查询目标ID
                        .eq("target", target)                // 过滤点赞类型
        );
        if (Objects.isNull(dbObjList) || dbObjList.isEmpty()) {
            return new ArrayList<>();
        }
        // 转换为Long类型
        return dbObjList.stream()
                .map(obj -> Long.parseLong(obj.toString()))
                .collect(Collectors.toList());
    }

    /**
     * 合并 Redis 中的目标 ID 和数据库中的目标 ID（避免遗漏）
     *
     * @param redisIds Redis中的ID集合
     * @param dbIds    数据库中的ID列表
     * @return 合并后的ID集合
     */
    private Set<Long> mergeTargetIds(Set<Long> redisIds, List<Long> dbIds) {
        Set<Long> allIds = new HashSet<>(redisIds); // 先添加Redis的ID
        if (Objects.nonNull(dbIds) && !dbIds.isEmpty()) {
            allIds.addAll(dbIds); // 再添加数据库的ID
        }
        return allIds;
    }

    /**
     * 单类型点赞数据同步逻辑（图片/评论共用）
     *
     * @param target             点赞类型（0=图片，1=评论）
     * @param likedUserKeyPrefix Redis点赞用户列表Key前缀
     * @param likeCountKeyPrefix Redis点赞数Key前缀
     * @param updateCountFunc    点赞数更新函数（更新Picture/Comment表的likeCount）
     * @param targetIds          待同步的目标ID集合（图片ID/评论ID）
     */
    private void handleTargetLikeSync(
            Integer target,
            String likedUserKeyPrefix,
            String likeCountKeyPrefix,
            BiFunction<Long, Long, Boolean> updateCountFunc,
            Set<Long> targetIds
    ) {
        for (Long targetId : targetIds) {
            log.debug("开始同步【{}】ID={}的点赞数据", target.equals(LikedTargetEnums.PICTURE.getValue()) ? "图片" : "评论", targetId);
            // 1. 构建当前目标的Redis Key
            // 点赞用户列表Key
            String userListKey = likedUserKeyPrefix + targetId;
            // 点赞数Key
            String countKey = likeCountKeyPrefix + targetId;
            // 2. 从Redis获取当前目标的点赞用户 ID 集合
            Set<String> redisUserStrSet = stringRedisTemplate.opsForSet().members(userListKey);
            Set<Long> redisUserIdSet = (Objects.nonNull(redisUserStrSet) && !redisUserStrSet.isEmpty())
                    ? redisUserStrSet.stream().map(Long::parseLong).collect(Collectors.toSet())
                    : new HashSet<>();

            // 3. 从数据库获取当前目标的已有点赞记录
            // 固定过滤点赞类型
            QueryWrapper<Liked> likedQueryWrapper = new QueryWrapper<Liked>().eq("target", target);
            // 根据目标类型设置 ID 字段（图片 = pictureId ，评论 = commentId）
            if (target.equals(LikedTargetEnums.PICTURE.getValue())) {
                likedQueryWrapper.eq("pictureId", targetId);
            } else if (target.equals(LikedTargetEnums.COMMENT.getValue())) {
                likedQueryWrapper.eq("commentId", targetId);
            }
            List<Liked> dbLikedList = likedService.list(likedQueryWrapper);
            Set<Long> dbUserIdSet = dbLikedList.stream()
                    .map(Liked::getUserId)
                    .collect(Collectors.toSet());

            // 情况 1：Redis中无点赞用户（需清空数据库记录）
            if (redisUserIdSet.isEmpty()) {
                // 若数据库有记录，删除所有关联点赞
                if (!dbUserIdSet.isEmpty()) {
                    boolean deleteSuccess = likedService.remove(likedQueryWrapper);
                    ThrowUtils.throwIf(!deleteSuccess, ErrorCode.OPERATION_ERROR,
                            String.format("删除【%s】ID=%s的点赞记录失败", target == 0 ? "图片" : "评论", targetId));
                    log.debug("【{}】ID={}已取消所有点赞，删除数据库{}条记录", target == 0 ? "图片" : "评论", targetId, dbUserIdSet.size());
                }
                // 更新目标的点赞数为 0（图片 → Picture表，评论→Comment表）
                boolean countUpdateSuccess = updateCountFunc.apply(targetId, 0L);
                ThrowUtils.throwIf(!countUpdateSuccess, ErrorCode.OPERATION_ERROR,
                        String.format("更新【%s】ID=%s的点赞数为0失败", target == 0 ? "图片" : "评论", targetId));
                // 清理Redis空Key（释放空间）
                stringRedisTemplate.delete(userListKey);
                stringRedisTemplate.delete(countKey);
                continue;
            }

            // 情况 2： Redis 中有点赞用户（处理新增/删除）
            // 4. 找出需新增的点赞用户（ Redis 有但数据库无）
            Set<Long> addUserIds = redisUserIdSet.stream()
                    .filter(userId -> !dbUserIdSet.contains(userId))
                    .collect(Collectors.toSet());
            // 5. 找出需删除的点赞用户（数据库有但Redis无）
            Set<Long> deleteUserIds = dbUserIdSet.stream()
                    .filter(userId -> !redisUserIdSet.contains(userId))
                    .collect(Collectors.toSet());
            // 6. 执行新增点赞记录
            if (!addUserIds.isEmpty()) {
                Date now = new Date();
                List<Liked> addLikedList = addUserIds.stream().map(userId -> {
                    Liked liked = new Liked();
                    liked.setUserId(userId);
                    liked.setTarget(target); // 点赞类型（0 = 图片，1 = 评论）
                    liked.setCreateTime(now);
                    liked.setEditTime(now);
                    liked.setUpdateTime(now);
                    // 根据目标类型设置ID字段
                    if (target.equals(LikedTargetEnums.PICTURE.getValue())) {
                        liked.setPictureId(targetId);
                    } else if (target.equals(LikedTargetEnums.COMMENT.getValue())) {
                        liked.setCommentId(targetId);
                    }
                    return liked;
                }).collect(Collectors.toList());
                likedService.saveBatch(addLikedList);
                log.debug("【{}】ID={}新增点赞记录{}条", target == 0 ? "图片" : "评论", targetId, addLikedList.size());
            }

            // 7. 执行删除点赞记录
            if (!deleteUserIds.isEmpty()) {
                QueryWrapper<Liked> deleteWrapper = new QueryWrapper<Liked>()
                        .eq("target", target)
                        // 过滤需删除的用户
                        .in("userId", deleteUserIds);
                // 根据目标类型设置ID字段
                if (target.equals(LikedTargetEnums.PICTURE.getValue())) {
                    deleteWrapper.eq("pictureId", targetId);
                } else if (target.equals(LikedTargetEnums.COMMENT.getValue())) {
                    deleteWrapper.eq("commentId", targetId);
                }
                boolean deleteSuccess = likedService.remove(deleteWrapper);
                ThrowUtils.throwIf(!deleteSuccess, ErrorCode.OPERATION_ERROR,
                        String.format("删除【%s】ID=%s的部分点赞记录失败",
                                target == 0 ? "图片" : "评论", targetId));
                log.debug("【{}】ID={}删除点赞记录{}条",
                        target == 0 ? "图片" : "评论", targetId, deleteUserIds.size());
            }

            // 8. 更新目标的点赞数（从 Redis 获取最新数量）
            String redisCountStr = stringRedisTemplate.opsForValue().get(countKey);
            Long finalLikeCount = Objects.nonNull(redisCountStr) ? Long.parseLong(redisCountStr) : 0L;
            boolean countUpdateSuccess = updateCountFunc.apply(targetId, finalLikeCount);
            ThrowUtils.throwIf(!countUpdateSuccess, ErrorCode.OPERATION_ERROR,
                    String.format("更新【%s】ID=%s的点赞数为%s失败", target == 0 ? "图片" : "评论", targetId, finalLikeCount));
            log.debug("【{}】ID={}点赞数更新为{}", target == 0 ? "图片" : "评论", targetId, finalLikeCount);
        }
    }
}


